package com.stox.client.ui.javafx.chartingy.model.chart;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;

import com.stox.client.ui.javafx.chartingy.model.axis.DateAxis;
import com.stox.client.ui.javafx.chartingy.model.enumeration.ChartType;
import com.stox.client.ui.javafx.chartingy.model.node.DataNode;
import com.stox.client.ui.javafx.chartingy.model.plot.Data;
import com.stox.client.ui.javafx.chartingy.model.plot.Plot;
import com.stox.client.ui.javafx.chartingy.model.plot.PricePlot;
import com.stox.client.ui.javafx.chartingy.view.ChartingView;
import com.stox.client.ui.javafx.core.StoxPlatform;
import com.stox.client.ui.javafx.core.event.listener.BarChangeListenerRegistry;
import com.stox.client.ui.javafx.widget.RangeSlider;
import com.stox.shared.core.model.Bar;
import com.stox.shared.core.model.Scrip;
import com.stox.shared.core.model.intf.Callback;

public class PrimaryChart extends Chart<Scrip, Bar> {

	private ReadOnlyObjectProperty<Bar> lastBarProperty;
	private final DateAxis dateAxis = new DateAxis(this);
	private final String subscriptionKey = UUID.randomUUID().toString();
	private final ChangeListener<Scrip> scripChangeListener = new ChangeListener<Scrip>(){
		@Override
		public void changed(ObservableValue<? extends Scrip> observable, Scrip oldValue, Scrip newValue) {
			unsubscribe(oldValue);
			subscribe(newValue);
		}
	};
	private final ChangeListener<Bar> lastBarChangeListener = new ChangeListener<Bar>(){
		
		@Override
		public void changed(ObservableValue<? extends Bar> observable, Bar oldValue, Bar newValue) {
			PricePlot plot = getPrimaryPlot();
			List<Bar> bars = plot.getLoadedBars();
			if(null == bars){
				bars = new ArrayList<>(1);
			}				
			if(!bars.isEmpty()){
				Bar bar = bars.get(0);
				Date date = getChartingView().getBarSpan().ceil(bar.getDate());
				if(newValue.getDate().before(date)){
					bars.set(0, newValue);
					if(ChartType.ALTERED.equals(getChartingView().getBarType().getChartType())){
						plot.setData(plot.createDatas(plot.translateBarType(bars)));
					}else if(null != plot.getData().get(0).getDataNode()){
						Data<Scrip, Bar> data = plot.getData().get(0);
						data.setDate(newValue.getDate().getTime());
						data.setExtraValue(newValue);
						data.setMax(Math.max(data.getMax(), newValue.getHigh()));
						data.setMin(Math.min(data.getMin(), newValue.getLow()));
						data.setValue(newValue.getClose());
						DataNode<Scrip,Bar> dataNode = data.getDataNode();
						
						StoxPlatform.runLater(new Runnable(){
							@Override
							public void run() {
								dataNode.updateColor();
								dataNode.layoutChartChildren();
							}
						});
						
					}
				}else{
					bars.add(0,newValue);
					plot.setData(plot.createDatas(plot.translateBarType(bars)));
				}
			}else{
				bars.add(newValue);
				plot.setData(plot.createDatas(plot.translateBarType(bars)));
			}
		}
	};

	public PrimaryChart(final ChartingView chartingView) {
		super(chartingView);
		setBottom(dateAxis);
		chartingView.autoUpdateProperty().addListener(new ChangeListener<Boolean>(){
			@Override
			public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
				if(newValue){
					subscribe(getPrimaryPlot().getExtraValue());
				}else{
					unsubscribe(getPrimaryPlot().getExtraValue());
				}
			}
		});
		chartingView.addOnCloseCallback(new Callback<Void,Void>(){
			@Override
			public Void execute(Void payload) {
				BarChangeListenerRegistry.INSTANCE.unsubscribe(subscriptionKey);
				return null;
			}
		});
		primaryPlotProperty().addListener(new ChangeListener<Plot<Scrip,Bar>>(){
			@Override
			public void changed(ObservableValue<? extends Plot<Scrip, Bar>> observable, Plot<Scrip, Bar> oldValue, Plot<Scrip, Bar> newValue) {
				if(null != oldValue){
					oldValue.extraValueProperty().removeListener(scripChangeListener);
					unsubscribe(oldValue.getExtraValue());
				}
				if(null != newValue){
					newValue.extraValueProperty().addListener(scripChangeListener);
					subscribe(newValue.getExtraValue());
				}
			}
		});
	}
	
	private void subscribe(Scrip scrip){
		if(null != scrip && null != scrip.getId() && getChartingView().isAutoUpdate()){
			if(null != lastBarProperty){
				lastBarProperty.removeListener(lastBarChangeListener);
			}
			lastBarProperty = BarChangeListenerRegistry.INSTANCE.subscribe(subscriptionKey, scrip.getId());
			if(null != lastBarProperty){
				lastBarProperty.addListener(lastBarChangeListener);
			}
		}
	}
	
	private void unsubscribe(Scrip scrip){
		if(null != scrip && null != scrip.getId()){
			if(null != lastBarProperty){
				lastBarProperty.removeListener(lastBarChangeListener);
			}
			BarChangeListenerRegistry.INSTANCE.unsubscribe(subscriptionKey, scrip.getId());
		}
	}

	@Override
	public void updateAxisBounds() {
		final Plot<Scrip, Bar> primaryPlot = getPrimaryPlot();
		final RangeSlider rangeSlider = getChartingView().getRangeSlider();
		if (null != primaryPlot) {
			final int size = primaryPlot.getData().size();
			final int lowerBoundIndex = (int) ((1 - rangeSlider.getLowValue()) * (size - 1));
			final int upperBoundIndex = (int) ((1 - rangeSlider.getHighValue()) * (size - 1));
			dateAxis.updateBoundIndices(upperBoundIndex, lowerBoundIndex);
			super.updateAxisBounds();
		}
	}

	public DateAxis getDateAxis() {
		return dateAxis;
	}

	@Override
	public PricePlot getPrimaryPlot() {
		return (PricePlot) super.getPrimaryPlot();
	}

}
